#include "TBVec2.h"

namespace tb
{

	// ================================
	// Constructors
	// ================================

	TB_INLINE Vec3::Vec3() 
	{

	}

	TB_INLINE Vec3::Vec3(float a_X, float a_Y, float a_Z)
	{ 
		x = a_X;
		y = a_Y;
		z = a_Z;
	}

	TB_INLINE Vec3::Vec3(float* a_Vector)
	{ 
		x = a_Vector[0];
		y = a_Vector[1];
		z = a_Vector[2];
	}

	TB_INLINE Vec3::Vec3(const Vec3& a_Copy)
	{
		x = a_Copy.x;
		y = a_Copy.y;
		z = a_Copy.z;
	}

	TB_INLINE Vec3::~Vec3()
	{

	}

	// ================================
	// Conversion
	// ================================

	TB_INLINE const Vec3& Vec3::ToOpenGLVector(float* a_Target) const
	{
		a_Target[0] = x;
		a_Target[1] = y;
		a_Target[2] = z;

		return *this;
	}

	TB_INLINE Vec2 Vec3::ToXY() const
	{
		return Vec2(x, y);
	}
	TB_INLINE Vec2 Vec3::ToYX() const
	{
		return Vec2(y, x);
	}
	TB_INLINE Vec2 Vec3::ToYZ() const
	{
		return Vec2(y, z);
	}
	TB_INLINE Vec2 Vec3::ToZY() const
	{
		return Vec2(z, y);
	}
	TB_INLINE Vec2 Vec3::ToZX() const
	{
		return Vec2(z, x);
	}
	TB_INLINE Vec2 Vec3::ToXZ() const
	{
		return Vec2(x, z);
	}

	// ================================
	// Assignment
	// ================================

	TB_INLINE Vec3& Vec3::operator = (const Vec3& a_Point)
	{
		x = a_Point.x;
		y = a_Point.y;
		z = a_Point.z;

		return *this;
	}
	TB_INLINE Vec3& Vec3::operator = (float a_Value)
	{
		x = y = z = a_Value;
		return *this;
	}

	TB_INLINE float& Vec3::operator [] (int a_Member)
	{
		return (&x)[a_Member];
	}

	TB_INLINE float Vec3::operator [] (int a_Member) const
	{
		return (&x)[a_Member];
	}

	// ================================
	// Operators
	// ================================

	TB_INLINE Vec3 Vec3::operator - ()
	{
		return (Vec3(-x, -y, -z));
	}


	TB_INLINE Vec3& Vec3::operator += (const Vec3& a_Point)
	{ 
		x += a_Point.x;
		y += a_Point.y;
		z += a_Point.z;

		return *this;
	}
	TB_INLINE Vec3 Vec3::operator + (const Vec3& a_Point) const
	{ 
		return Vec3(x + a_Point.x, y + a_Point.y, z + a_Point.z);
	}

	TB_INLINE Vec3& Vec3::operator -= (const Vec3& a_Point)
	{ 
		x -= a_Point.x;
		y -= a_Point.y;
		z -= a_Point.z;

		return *this;
	}
	TB_INLINE Vec3 Vec3::operator - (const Vec3& a_Point) const
	{
		return Vec3(x - a_Point.x, y - a_Point.y, z - a_Point.z);
	}

	TB_INLINE Vec3& Vec3::operator *= (float a_Scalar)
	{ 
		x *= a_Scalar;
		y *= a_Scalar;
		z *= a_Scalar;

		return *this;
	}
	TB_INLINE Vec3& Vec3::operator *= (const Vec3& a_Point)
	{ 
		x *= a_Point.x;
		y *= a_Point.y;
		z *= a_Point.z;

		return *this;
	}
	TB_INLINE Vec3 Vec3::operator * (float a_Value) const
	{ 
		return Vec3(x * a_Value, y * a_Value, z * a_Value);
	}
	TB_INLINE Vec3 Vec3::operator * (Vec3& a_Point) const
	{ 
		return Vec3(x * a_Point.x, y * a_Point.y, z * a_Point.z);
	}

	TB_INLINE Vec3& Vec3::operator /= (float a_Value)				
	{ 
		x /= a_Value;
		y /= a_Value;
		z /= a_Value;

		return *this;
	}
	TB_INLINE Vec3& Vec3::operator /= (const Vec3& a_Point)		
	{ 
		x /= a_Point.x;
		y /= a_Point.y;
		z /= a_Point.z;

		return *this;
	}
	TB_INLINE Vec3 Vec3::operator / (float a_Value) const
	{ 
		const float rcp = 1 / a_Value;
		return Vec3(x * rcp, y * rcp, z * rcp);
	}
	TB_INLINE Vec3 Vec3::operator / (const Vec3& a_Point) const
	{ 
		return Vec3(x / a_Point.x, y / a_Point.y, z / a_Point.z);
	}

	// ================================
	// Comparison
	// ================================

	TB_INLINE bool Vec3::operator == (const Vec3& a_Point)
	{			
		if (x != a_Point.x || y != a_Point.y || z != a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator == (float a_Value)
	{
		if (x != a_Value || y != a_Value || z != a_Value) { return false; }
		return true;
	}


	TB_INLINE bool Vec3::operator != (const Vec3& a_Point)
	{
		if (x == a_Point.x || y == a_Point.y || z == a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator != (float a_Value)
	{
		if (x == a_Value || y == a_Value || z == a_Value) { return false; }
		return true;
	}


	TB_INLINE bool Vec3::operator > (const Vec3& a_Point)
	{
		if (x <= a_Point.x || y <= a_Point.y || z <= a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator > (float a_Value)
	{
		if (x <= a_Value || y <= a_Value || z <= a_Value) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator >= (const Vec3& a_Point)
	{
		if (x < a_Point.x || y < a_Point.y || z < a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator >= (float a_Value)
	{
		if (x < a_Value || y < a_Value || z < a_Value) { return false; }
		return true;
	}


	TB_INLINE bool Vec3::operator < (const Vec3& a_Point)
	{
		if (x >= a_Point.x || y >= a_Point.y || z >= a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator < (float a_Value)
	{
		if (x >= a_Value || y >= a_Value || z >= a_Value) { return false; }
		return true;
	}

	TB_INLINE bool Vec3::operator <= (const Vec3& a_Point)
	{
		if (x > a_Point.x || y > a_Point.y || z > a_Point.z) { return false; }
		return true;
	}
	TB_INLINE bool Vec3::operator <= (float a_Value)
	{
		if (x > a_Value || y > a_Value || z > a_Value) { return false; }
		return true;
	}

	// ================================
	// Functions
	// ================================

	TB_INLINE Vec3& Vec3::Clear()
	{
		x = y = z = 0.f;
		return *this;
	}

	TB_INLINE float Vec3::GetSquaredLength() const
	{
		return ((x * x) + (y * y) + (z * z));
	}

	TB_INLINE float Vec3::GetLength() const
	{
		return (Math::Sqrt((x * x) + (y * y) + (z * z)));
	}

	TB_INLINE float Vec3::GetSquaredDistanceBetween(const Vec3& a_Other) const
	{
		const float diffX = x - a_Other.x;
		const float diffY = y - a_Other.y;
		const float diffZ = z - a_Other.z;

		return (
			(diffX * diffX) + 
			(diffY * diffY) + 
			(diffZ * diffZ)
		);
	}

	TB_INLINE float Vec3::GetDistanceBetween(const Vec3& a_Other) const
	{
		return (Math::Sqrt(GetSquaredDistanceBetween(a_Other)));
	}

	TB_INLINE Vec3& Vec3::Normalize()
	{
		const float length = 1.f / GetLength();

		x *= length;
		y *= length;
		z *= length;

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetNormalized() const
	{
		const float length = 1.f / GetLength();

		return (Vec3(x * length, y * length, z * length));
	}

	TB_INLINE float Vec3::GetDotProduct(const Vec3& a_Other) const
	{
		return (
			(x * a_Other.x) +
			(y * a_Other.y) +
			(z * a_Other.z)
		);
	}

	TB_INLINE Vec3& Vec3::CrossProduct(const Vec3& a_Other)
	{
		const float oldx = x;
		const float oldy = y;
		const float oldz = z;

		x = (oldy * a_Other.z) - (oldz * a_Other.y);
		y = (oldz * a_Other.x) - (oldx * a_Other.z);
		z = (oldx * a_Other.y) - (oldy * a_Other.x);

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetCrossProduct(const Vec3& a_Other) const
	{
		Vec3 temp;

		temp.x = (y * a_Other.z) - (z * a_Other.y);
		temp.y = (z * a_Other.x) - (x * a_Other.z);
		temp.z = (x * a_Other.y) - (y * a_Other.x);

		return temp;
	}

	TB_INLINE Vec3& Vec3::Reverse()
	{
		x *= -1.f;
		y *= -1.f;
		z *= -1.f;

		return *this;
	}
	TB_INLINE Vec3 Vec3::GetReverse() const
	{
		return (Vec3(-x, -y, -z));
	}

	TB_INLINE Vec3& Vec3::Reciprocral()
	{
		x = 1 / x;
		y = 1 / y;
		z = 1 / z;

		return *this;
	}
	TB_INLINE Vec3 Vec3::GetReciprocal() const
	{
		return (Vec3(1 / x, 1 / y, 1 / z));
	}

	TB_INLINE Vec3& Vec3::Min(const Vec3& a_Other)
	{
		x = (x < a_Other.x) ? x : a_Other.x;
		y = (y < a_Other.y) ? y : a_Other.y;
		z = (z < a_Other.z) ? z : a_Other.z;

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetMin(const Vec3& a_Other) const
	{
		Vec3 temp(x, y, z);
		temp.Min(a_Other);
		return temp;
	}

	TB_INLINE Vec3& Vec3::Max(const Vec3& a_Other)
	{
		x = (x > a_Other.x) ? x : a_Other.x;
		y = (y > a_Other.y) ? y : a_Other.y;
		z = (z > a_Other.z) ? z : a_Other.z;

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetMax(const Vec3& a_Other) const
	{
		Vec3 temp(x, y, z);
		temp.Max(a_Other);
		return temp;
	}

	TB_INLINE Vec3& Vec3::Clamp(float a_Min, float a_Max)
	{
		if (x < a_Min) { x = a_Min; }
		if (y < a_Min) { y = a_Min; }
		if (z < a_Min) { z = a_Min; }

		if (x > a_Max) { x = a_Max; }
		if (y > a_Max) { y = a_Max; }
		if (z > a_Max) { z = a_Max; }

		return *this;
	}
	TB_INLINE Vec3 Vec3::GetClamped(float a_Min, float a_Max) const
	{
		return Vec3(
			(x < a_Min) ? a_Min : (x > a_Max) ? a_Max : x,
			(y < a_Min) ? a_Min : (y > a_Max) ? a_Max : y,
			(z < a_Min) ? a_Min : (z > a_Max) ? a_Max : z
		);
	}

	TB_INLINE Vec3& Vec3::Reflect(const Vec3& a_Normal)
	{
		float dot = GetDotProduct(a_Normal) * 2.f;

		x = ((a_Normal.x * dot) - x) * -1.f;
		y = ((a_Normal.y * dot) - y) * -1.f;
		z = ((a_Normal.z * dot) - z) * -1.f;

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetReflected(const Vec3& a_Normal) const
	{
		return (((a_Normal * GetDotProduct(a_Normal) * 2.f) - *this) * -1);
	}

	TB_INLINE Vec3 Vec3::GetHalfWayVector(const Vec3& a_Other) const
	{
		Vec3 halfway(x + a_Other.x, y + a_Other.y, z + a_Other.z);
		halfway.Normalize();

		return halfway;
	}

	TB_INLINE Vec3& Vec3::Randomize(float a_Min, float a_Max)
	{
		x = Math::Random(a_Min, a_Max);
		y = Math::Random(a_Min, a_Max);
		z = Math::Random(a_Min, a_Max);

		return *this;
	}

	TB_INLINE Vec3& Vec3::Lerp(const Vec3& a_Other, float a_Amount)
	{
		float ox = x;
		float oy = y;
		float oz = z;

		x = ox + (a_Other.x - ox) * a_Amount;
		y = oy + (a_Other.y - oy) * a_Amount;
		z = oz + (a_Other.z - oz) * a_Amount;

		return *this;
	}

	TB_INLINE Vec3 Vec3::GetLerp(const Vec3& a_Other, float a_Amount) const
	{
		return Vec3 (
			x + (a_Other.x - x) * a_Amount,
			y + (a_Other.y - y) * a_Amount,
			z + (a_Other.z - z) * a_Amount
		);
	}

	TB_INLINE Vec3 Vec3::GetSphericalInterpolationNormalized(const Vec3& a_Other, float a_Percentage)
	{
		/*const float angle = ACOS(GetDotProduct(a_Other));
		const float sinm = SIN((1 - a_Percentage) * angle);
		const float sino = SIN(a_Percentage * angle);
		return (((*this * sinm) + (a_Other * sino)) / SIN(angle));*/

		return Vec3(0);
	}

}